package cn.edu.hit.kg;

import org.apache.commons.math3.linear.MatrixUtils;
import org.apache.commons.math3.linear.RealMatrix;
import org.apache.commons.math3.linear.RealVector;

import java.util.*;
import java.util.stream.IntStream;

public class LinearEquationSolver {
    private RealMatrix matrix;
    private RealVector constant;
    private RealMatrix x_shift;//用于列变换后 X 与 X' 的映射矩阵，其中 X = x_shift * X'
    private int m;
    private int n;

    public LinearEquationSolver(int m, int n, RealMatrix matrix, RealVector constant) {
        this.matrix = matrix;
        this.constant = constant;
        this.m = m;
        this.n = n;
        this.x_shift = MatrixUtils.createRealIdentityMatrix(n);
    }

    private void exchangeRow(int i, int j) {
        RealMatrix rowMatrix = MatrixUtils.createRealIdentityMatrix(m);
        rowMatrix.setEntry(i, i, 0);
        rowMatrix.setEntry(j, j, 0);
        rowMatrix.setEntry(i, j, 1);
        rowMatrix.setEntry(j, i, 1);
        matrix = rowMatrix.multiply(matrix);
        constant = rowMatrix.operate(constant);
    }

    private void shiftRow(int fromRow, int targetRow, double mutiplyFactor) {
        RealMatrix rowMatrix = MatrixUtils.createRealIdentityMatrix(m);
        rowMatrix.setEntry(targetRow, fromRow, mutiplyFactor);
        matrix = rowMatrix.multiply(matrix);
        constant = rowMatrix.operate(constant);
    }

    private void scalarMutiplyRow(int row, double factor) {
        RealMatrix rowMatrix = MatrixUtils.createRealIdentityMatrix(m);
        rowMatrix.setEntry(row, row, factor);
        matrix = rowMatrix.multiply(matrix);
        constant = rowMatrix.operate(constant);
    }

    private void exchangeColumn(int i, int j) {
        RealMatrix rowMatrix = MatrixUtils.createRealIdentityMatrix(n);
        rowMatrix.setEntry(i, i, 0);
        rowMatrix.setEntry(j, j, 0);
        rowMatrix.setEntry(i, j, 1);
        rowMatrix.setEntry(j, i, 1);
        matrix = rowMatrix.preMultiply(matrix);
        x_shift = rowMatrix.multiply(x_shift);
    }

    @Override
    public String toString() {
        return "LinearEquationSolver{" +
                "matrix=" + matrix +
                ", constant=" + constant +
                ", x_shift=" + x_shift +
                ", m=" + m +
                ", n=" + n +
                '}';
    }

    //标准化为上三角阵，且非零行、列的主对角线元素为1
    public void standardize() {
        int m_squre = Math.min(m, n);
        for (int row = 0; row < m_squre; row++) {
            if (matrix.getEntry(row, row) == 0) {
                //主对角线上元素为0，需进行 行列的变换
                int finalRow = row;
                boolean col_not_all_zero = IntStream.range(row, m).anyMatch(r -> matrix.getEntry(r, finalRow) != 0);
                if (!col_not_all_zero) {
                    //该列全为0，需与后面的一列交换
                    boolean matrix_all_zero = true;
                    for (int column = row + 1; column < n; column++) {
                        int finalColumn = column;
                        if (IntStream.range(row, m).anyMatch(r -> matrix.getEntry(r, finalColumn) != 0)) {
                            // 交换第row列与第column列
                            matrix_all_zero = false;
                            exchangeColumn(row, column);
                            break;
                        }
                    }
                    if (matrix_all_zero) {
                        break;
                    }
                }//end of if ---列全为0时的处理

                if (matrix.getEntry(row, row) == 0) {
                    //进行行交换，使主对角线上元素不为0
                    int finalRow1 = row;
                    int r = IntStream.range(row + 1, m).filter(rx -> matrix.getEntry(rx, finalRow1) != 0).findAny().getAsInt();
                    exchangeRow(row, r);
                }
            }//end of if -- 主对角线上元素为0时的处理

            //主对角线上元素归一化
            scalarMutiplyRow(row, 1.0 / matrix.getEntry(row, row));

            //使其它行变为0
            for (int i = row + 1; i < m; i++) {
//                if (i!=row)
                shiftRow(row, i, -matrix.getEntry(i, row));
            }
        }
    }

    //方阵化，求出基础解系
    public RealVector squarize() {
        RealMatrix square = extendToSquare(m, n, matrix);
        List<RealVector> vectors = new ArrayList<>();
        for (int i = 0; i < square.getData().length; i++) {
            if (square.getEntry(i, i) == 0) {
                for (int i1 = i - 1; i1 >= 0; i1--) {
                    square.setEntry(i1, i, -square.getEntry(i1, i));
                }
                square.setEntry(i, i, 1);
                vectors.add(square.getColumnVector(i));
            }
        }

        System.out.println("before reshift X:");
        System.out.println(vectors);
        System.out.println(constant);
        System.out.println(x_shift);

        System.out.println("after reshift X:");
        vectors.stream()
                .map(vector -> x_shift.operate(vector))
                .forEach(System.out::println);
        RealMatrix convert = getExtendMatrix(m, n);
        RealVector solved = constant;
        if (m < n) {
            solved = convert.operate(constant);
        }
//        System.out.println(solved);
        solved = x_shift.operate(solved);
        System.out.println(solved);
        return solved;
    }

    public static RealMatrix extendToSquare(int m, int n, RealMatrix matrix) {
//        int max = Math.max(m,n);
//        int min = Math.min(m,n);
        if (n == m) {
            return matrix;
        }
        RealMatrix convert = getExtendMatrix(m, n);
        if (m < n) {
            return convert.multiply(matrix);
        } else {
            return matrix.multiply(convert);
        }
    }

    private static RealMatrix getExtendMatrix(int m, int n) {
        int min = Math.min(m, n);
        RealMatrix convert;
        if (m < n) {
            convert = MatrixUtils.createRealMatrix(n, m);
        } else {
            convert = MatrixUtils.createRealMatrix(m, n);
        }
        for (int i = 0; i < min; i++) {
            convert.setEntry(i, i, 1);
        }
        return convert;
    }

    public static void main(String[] args) {
//        test();
        double[] rowRequire = new double[]{0.85, 0.75, 0.2};
        double[] columnRequire = new double[]{0.65, 0.75};
        double[][] matrix = new double[][]{
                {0.8, 0.9},
                {0.75, 0.65},
                {0.45, 0.95}
        };

        int totalD = rowRequire.length + columnRequire.length;
        int restrict_size = rowRequire.length * columnRequire.length + totalD;
        int x_size = 1 << totalD;//n
        double[][] data = new double[restrict_size][x_size];
        for (int m = 0; m < rowRequire.length; m++) {
            int mt = 1 << m;
            for (int n = 0; n < columnRequire.length; n++) {
                int nt = 1 << n;
                for (int mm = 0; mm < (1 << rowRequire.length); mm++) {
                    for (int nn = 0; nn < (1 << columnRequire.length); nn++) {
                        if ((mm & mt) != 0 && (nn & nt) != 0) {
                            System.out.println("row:" + (m * columnRequire.length + n));
                            System.out.println("column:" + ((mm << columnRequire.length) + nn));
                            data[m * columnRequire.length + n][(mm << columnRequire.length) + nn] = 1;
                        }
                    }
                }
            }
        }

        for (int m = 0; m < rowRequire.length; m++) {
            int mt = 1 << m;
            for (int mm = 0; mm < (1 << rowRequire.length); mm++) {
                if ((mm & mt) != 0) {
                    for (int nn = 0; nn < (1 << columnRequire.length); nn++) {
                        data[rowRequire.length * columnRequire.length + m][(mm << columnRequire.length) + nn] = 1;
                    }
                }
            }
        }

        for (int n = 0; n < columnRequire.length; n++) {
            int nt = 1 << n;
            for (int nn = 0; nn < (1 << columnRequire.length); nn++) {
                if ((nn & nt) != 0) {
                    for (int mm = 0; mm < (1 << rowRequire.length); mm++) {
                        data[rowRequire.length * columnRequire.length + rowRequire.length + n][(mm << columnRequire.length) + nn] = 1;
                    }
                }
            }
        }

        System.out.println("计算前的A:");
        System.out.println(Arrays.deepToString(data));

        double[] b_list = new double[restrict_size];
        for (int m = 0; m < rowRequire.length; m++) {
            for (int n = 0; n < columnRequire.length; n++) {
                b_list[m * columnRequire.length + n] = matrix[m][n];
            }
        }
        for (int m = 0; m < rowRequire.length; m++) {
            b_list[rowRequire.length * columnRequire.length + m] = rowRequire[m];
        }
        for (int n = 0; n < columnRequire.length; n++) {
            b_list[rowRequire.length * columnRequire.length + rowRequire.length + n] = columnRequire[n];
        }

        System.out.println("计算前的B:");
        System.out.println(Arrays.toString(b_list));

        LinearEquationSolver solver = new LinearEquationSolver(restrict_size, x_size, MatrixUtils.createRealMatrix(data), MatrixUtils.createRealVector(b_list));
        solver.standardize();
        System.out.println(solver);

        RealVector result = solver.squarize();
        double sumAll = Arrays.stream(result.toArray()).sum();
        result = result.map(x -> Math.round(x / sumAll * 1000));

        System.out.println("当前的分布：");
        System.out.println(result);
        System.out.println("初始化完毕。\n_________________________________________________________________________\n");

        Scanner scanner = new Scanner(System.in);
        outer_while:
        while (true) {
            System.out.println("输入特征测试？(yes/quit)");
            String test = scanner.next();
            if (test.equals("yes")) {
                System.out.println("请输入一组特征：(" + rowRequire.length + "个0/1)");

//        int[] features = new int[rowRequire.length];
                int feature_num = 0;
                for (int m = 0; m < rowRequire.length; m++) {
                    while (true) {
                        int k = scanner.nextInt();
                        if (k != 0 && k != 1) {
                            System.out.println("只允许输入0/1");
                        } else {
//                    features[m] = k;
                            if (k == 1) {
                                feature_num += 1 << m;
                            }
                            break;
                        }
                    }
                }

//                System.out.println("计算各原因概率如下：");
                System.out.println("ID\t保守概率\t相对概率\t均衡概率");
//                System.out.println("ID\t保守概率");
                double[][] props = new double[columnRequire.length][3];
                int[][] total_ps = new int[columnRequire.length][3];
                for (int n = 0; n < columnRequire.length; n++) {
                    int eg_count = 0, eg_rf = 0;
                    int em_count = 0, em_rf = 0;
                    int er_count = 0, er_rf = 0;
                    for (int i = 0; i < result.toArray().length; i++) {
                        int sub_f = (i >> columnRequire.length);
                        if (sub_f == feature_num) {
                            eg_count += result.getEntry(i);
                            if ((i & (1 << n)) > 0) {
                                eg_rf += result.getEntry(i);
                            }
                        }

                        if ((sub_f & feature_num) == sub_f) {
                            em_count += result.getEntry(i);
                            if ((i & (1 << n)) > 0) {
                                em_rf += result.getEntry(i);
                            }
                        }

                        for (int row = 0; row < rowRequire.length; row++) {
                            if (((1 << row) & sub_f) == ((1 << row) & (sub_f & feature_num))) {
                                er_count += result.getEntry(i);
                                if ((i & (1 << n)) > 0) {
                                    er_rf += result.getEntry(i);
                                }
                                break;
                            }
                        }
                    }
                    total_ps[n][0] = eg_count;
                    total_ps[n][1] = em_count;
                    total_ps[n][2] = er_count;

                    props[n][0] = eg_rf * 1.0 / eg_count;
                    props[n][1] = em_rf * 1.0 / em_count;
                    props[n][2] = er_rf * 1.0 / er_count;

                    System.out.printf("%d\t%.2f\t%.2f\t%.2f\n", n + 1, props[n][0], props[n][1], props[n][2]);
//                    System.out.printf("%d\t%.2f\n",n+1,eg_count*0.001);
                }

                while_loop:
                while (true) {
                    String ques = null;
                    System.out.println("是否进行专家检验?(多个原因，可进行多轮，每次针对一个错误的原因)(yes/quit)");
                    ques = scanner.nextLine();

                    switch (ques) {
                        case "yes": {
                            System.out.println("输入专家的判断：");
                            int pp[] = new int[columnRequire.length];
                            for (int m = 0; m < columnRequire.length; m++) {
                                while (true) {
                                    int k = scanner.nextInt();
                                    if (k != 0 && k != 1) {
                                        System.out.println("只允许输入0/1");
                                    } else {
//                    features[m] = k;
                                        if (k == 1) {
                                            pp[m] = 1;
                                        }
                                        break;
                                    }
                                }
                            }
                            double max_trans = 0;
                            for (int n = 0; n < columnRequire.length; n++) {
                                //暂只以第二种策略进行调整：
                                System.out.printf("pp[n]=%d,prop=%.2f\n", pp[n], props[n][1]);
                                if (pp[n] == 1 && props[n][1] > 0.5) {
                                    System.out.println("原因" + (n + 1) + "匹配");
                                } else if (pp[n] == 0 && props[n][1] < 0.5) {
                                    System.out.println("原因" + (n + 1) + "匹配");
                                } else {
                                    System.out.println("原因" + (n + 1) + "不匹配，待调整。");
                                    max_trans = Math.max(Math.max(Math.abs(props[n][1] - 0.5), 0.01), max_trans);
                                    max_trans = Math.min(max_trans * (1000.0 / total_ps[n][1] - 1), 0.1);
                                }
                            }

                            //进行调整：
                            int toModifyCount = (int) (1000 * max_trans);
                            System.out.println("调整幅度：" + toModifyCount);
                            Random random = new Random();
                            int step = random.nextInt(x_size);
                            for (int i = 0; i < toModifyCount; i++) {
                                if (result.getEntry(step) > 0) {
                                    result.setEntry(step, result.getEntry(step) - 1.0);
                                    step = random.nextInt(x_size);
                                } else {
                                    i--;
                                    step++;
                                    step = step % x_size;
                                }
                            }
                            int index = feature_num << columnRequire.length;
                            for (int i = 0; i < pp.length; i++) {
                                index += pp[i] * (1 << i);
                            }
//                            System.out.printf("%b\n",);
                            result.setEntry(index, result.getEntry(index) + toModifyCount);

                            System.out.println("调整完毕！调整后分布为：");
                            System.out.println(result);
                        }
                        break;
                        case "quit":
                            break while_loop;
                        default:
                            System.out.println("请输入yes/no/quit中的一个");

                    }
                }
            } else if (test.equals("quit")) {
                break outer_while;
            }
        }


    }

    private static void test() {
        //        double[][] data = new double[][]{
//                {2, 1, -1, 1},
//                {4, 2, -1, 1},
//                {2, 2, -1, 1}
//        };
        double[][] data = new double[][]{
                {2, 1, -1, 1},
                {4, 2, -1, 1},
                {2, 1, -1, 1}
        };
        double[] bs = new double[]{1, 2, 1};

        RealMatrix matrix = MatrixUtils.createRealMatrix(data);
        RealVector vector = MatrixUtils.createRealVector(bs);

//        RealMatrix matrix1 = extendToSquare(3,4,matrix);
//        System.out.println(matrix1);

        LinearEquationSolver solver = new LinearEquationSolver(3, 4, matrix, vector);
        System.out.println(solver);

//        solver.shiftRow(0, 1, -2);
//        System.out.println(solver);
//
//        solver.exchangeRow(1, 2);
//        System.out.println(solver);
//
//        solver.exchangeColumn(1, 2);
//        System.out.println(solver);

        solver.standardize();
        System.out.println(solver);

        solver.squarize();
    }
}
